package jeconbond.economic.market.strategy;

import java.util.HashMap;
import java.util.Map;

import jeconbond.economic.agent.IEconomicProducer;
import jeconbond.economic.agent.service.IPreActServiceAgent;
import jeconbond.economic.resources.IResourceType;
import jeconbond.economic.resources.ResourceStorrage;
import jeconbond.economic.resources.ResourceUtils;
import jeconbond.economic.resources.ResourceBasket;
import jeconbond.economic.systems.market.IMoneyAccount;
import jeconbond.economic.systems.market.IMarketResourceRedestributionSystem;
import jeconbond.economic.production.IResourceNecessityInformer;
import jeconbond.economic.strategy.IMerchant;

/**
 * Utility class 4 tracking costs and produced resoutces.
 */
public class ObserveCostsProfitMerchantDecorator implements IMerchant, IPreActServiceAgent, IPerformPriceStrategy {
	private static final double EPS = 1e-5;
	public double eps = EPS;
	
	private IMerchant submerchant;
	protected IMoneyAccount money;
	private ResourceStorrage outputRS;

	// trace info
	private double[] costsTrace;
	private double[] prodTrace;
	private IResourceType[] goalsTrace;
	private int traceLength;
	private Map<IResourceType, double[]> goal2costBasedPrice =
		new HashMap<IResourceType, double[]>();
	protected int tracePos;

	// state spy info
	private ResourceStorrage preProdOutputRSSnapshot = new ResourceStorrage();
	private ResourceStorrage resourceStorrTMPBuffer = new ResourceStorrage();

	public ObserveCostsProfitMerchantDecorator(
			IMerchant submerchant,
			IMoneyAccount money,
			int traceLength,
			ResourceStorrage outputRS
	) {
		initialise(submerchant, money, traceLength, outputRS);
	}

	public ObserveCostsProfitMerchantDecorator(
			IEconomicProducer agent,
			IResourceNecessityInformer informer,
			IMarketResourceRedestributionSystem mrrs,
			IMoneyAccount money,
			int traceLength,
			double purchaseLevel
	) {
		this(
				agent.getInputRS(),
				agent.getOutputRS(),
				informer,
				mrrs,
				money,
				traceLength,
				purchaseLevel
		);
	}

	public ObserveCostsProfitMerchantDecorator(
			ResourceStorrage inputRS,
			ResourceStorrage outputRS,
			IResourceNecessityInformer informer,
			IMarketResourceRedestributionSystem mrrs,
			IMoneyAccount money,
			int traceLength,
			double purchaseLevel
	) {
		ProductNecessityMerchant pnMerch = new ProductNecessityMerchant(
				mrrs,
				money,
				inputRS,
				outputRS,
				informer,
				purchaseLevel
		);
		pnMerch.setPerformPriceStrategy(this);
		initialise(pnMerch, money, traceLength, outputRS);
	}

	public IMerchant getSubmerchant() {
		return submerchant;
	}

	@Override
	public void processPurchase() {
		double moneyPrev = money.getBalance();
		submerchant.processPurchase();
		double cost = moneyPrev - money.getBalance();
		writeCost(cost);
	}

	@Override
	public void processSelling() {
		submerchant.processSelling();
	}

	@Override
	public void processPreProduceStage() {}

	@Override
	public void processProduceStage() {
		preProdOutputRSSnapshot.clear();
		ResourceUtils.addStorrages(preProdOutputRSSnapshot, outputRS);
	}

	@Override
	public void processPostProduceStage() {
		resourceStorrTMPBuffer.clear();
		ResourceUtils.addStorrages(resourceStorrTMPBuffer, outputRS);
		ResourceUtils.substractStorrages(
				resourceStorrTMPBuffer,
				preProdOutputRSSnapshot,
				false
		);
		double prodVal = 0.0;
		for (ResourceBasket rb : resourceStorrTMPBuffer.listBaskets()) {
			if (rb.value > eps) {
				prodVal += rb.value;
			}
		}
		double cost = costsTrace[tracePos];
		if (prodVal > eps) {
			for (ResourceBasket rb : resourceStorrTMPBuffer.listBaskets()) {
				if (rb.value > eps) {
					double part = rb.value / prodVal;
					costsTrace[tracePos] = cost * part;
					writeProduced(rb.value, rb.type);
				}
			}
		}
//		ResourceBasket prodRB = getProduced(resourceStorrTMPBuffer);
//		if (prodRB != null) {
//			writeProduced(prodRB.value, prodRB.type);
//		}
	}

	@Override
	public double performPrice(IResourceType resourceType) {
		double[] costBasedPrice_ptr = goal2costBasedPrice.get(resourceType);
		if (costBasedPrice_ptr == null) {
			return Double.NaN;
		}
		return costBasedPrice_ptr[0];
	}

	@Override
	public void setProcessDealStrategy(IProcessDealStrategy processDealStrategy) {
		submerchant.setProcessDealStrategy(processDealStrategy);
	}

	@Override
	public IMoneyAccount getMoneyAccount() {
		return submerchant.getMoneyAccount();
	}

	public double getLastCost() {
		return costsTrace[tracePos];
	}

	private void initialise(IMerchant submerchant, IMoneyAccount money, int traceLength, ResourceStorrage outputRS) {
		this.submerchant = submerchant;
		this.money = money;
		this.traceLength = traceLength;
		this.outputRS = outputRS;
		costsTrace = new double[traceLength];
		prodTrace = new double[traceLength];
		goalsTrace = new IResourceType[traceLength];
		tracePos = 0;
	}

//	private ResourceBasket getProduced(ResourceStorrage storrage) {
//		int countProd = 0;
//		ResourceBasket result = null;
//		for (ResourceBasket rb : storrage.listBaskets()) {
//			if (rb.value > eps) {
//				countProd++;
//				result = rb;
//			}
//		}
//		if (countProd > 1) throw new RuntimeException("Unexpected");
//		return result;
//	}

	public void writeProduced(double producedVal, IResourceType producedType) {
		prodTrace[tracePos] += producedVal;
		goalsTrace[tracePos] = producedType;
		increaseTracePos();
	}

	private void writeCost(double cost) {
		costsTrace[tracePos] += cost;
	}

	private void increaseTracePos() {
		computeCostBasedPrice(goalsTrace[tracePos]);
		if (prodTrace[tracePos] < 1e-3) {
			return;
		}
		tracePos++;
		if (tracePos == traceLength) {
			tracePos = 0;
		}
		prodTrace[tracePos] = 0.0;
		costsTrace[tracePos] = 0.0;
		goalsTrace[tracePos] = null;
	}

	private void computeCostBasedPrice(IResourceType goal) {
		double costs = 0.0;
		double produced = 0.0;
		for (int i = 0; i < traceLength; i++) {
			if (goalsTrace[i] == goal) {
				costs += costsTrace[i];
				produced += prodTrace[i];
			}
		}
		if (produced == 0.0) {
			throw new RuntimeException("Unexpected.");
		}
		double averCosts = costs / produced;
		double[] costBasedPrice_ptr = goal2costBasedPrice.get(goal);
		if (costBasedPrice_ptr == null) {
			costBasedPrice_ptr = new double[1];
			goal2costBasedPrice.put(goal, costBasedPrice_ptr);
		}
		costBasedPrice_ptr[0] = averCosts;
	}
}
